# 小智 AI Server Architecture Plan > 文档:小智 AI Server —— Task、语音分析与用户评估平台架构 Plan > Version: V1.1 > Date: 2026-08-30 > 本文在 V1.0 概念稿上,依据《Xiaozhi AI 开放应用平台 Master Plan》与《Architecture Audit》修订。 > **只设计,不编码。** > 与《小智 AI 硬件开放应用平台 —— 总体架构与开发 Plan》V1.1 成对阅读。 --- ## 0. 本文定位 本文只定义 **Server**(仓库 `xiaozhi-esp32-server`)如何从「实时语音对话后端」演进为「Task 编排 + 语音对象 + 评估」平台。 硬件执行细节见 Client Plan。本文只规定 **对设备的契约** 与 **云端模块 / 数据 / API**。 工作区另有 `smartlearning/`(课程 JSON + MQTT DOWNLOAD/PLAY_MEDIA)。它更接近 App Sync,但**未接入** Xiaozhi Server。本文把它当作可吸收的旁路,而不是第三套长期业务云。 **相对 V1.0 的关键修正:** | V1.0 | V1.1(本次) | |------|----------------| | 一上来拆 Gateway/一堆 Assessment 微服务 | **逻辑模块分开,物理仍是 Python xiaozhi-server + Java manager-api** | | Phase 1 就做跟读+倒背+口算三套评估 | Phase 1 = 英语复读编排且 **不评估**;Phase 2 = 跟读 + ASR 文本比对 | | 设备与 Server 各有一套完整 Task Runtime | **权威 Task Runtime 只在 Server**;设备是 Task Executor | | 音频规划为对象存储,但未对齐现状 | 承认现状是 `ai_agent_chat_audio` LONGBLOB;演进再迁对象存储 | | Agent 未区分 | **Agent ≠ App**;Agent 继续做人设/模型配置 | | 8~10 个平台阶段混在一起 | 与 Client 对齐为 Phase 1/2/3,其后评估平台再拆子阶段 | --- ## 1. 当前 Server 是什么(Audit 事实) 双栈,不是微服务网: | 模块 | 入口 | 职责 | |------|------|------| | xiaozhi-server | `main/xiaozhi-server/app.py` | WS `/xiaozhi/v1/`、OTA HTTP、实时 ASR/LLM/TTS | | manager-api | `AdminApplication.java` | 用户/设备/Agent/插件/聊天落库 | | manager-web / mobile | — | 管理端 | | digital-human | — | 测试端,非主链路 | 一次对话: ```text Device → ConnectionHandler (session_id = uuid4) → VAD → ASR → intent / chat() → UnifiedToolHandler (Plugin / MCP / IoT) → TTS → type=tts/stt/llm/mcp + Opus → 可选 report → POST /agent/chat-history/report → ai_agent_chat_history + ai_agent_chat_audio (WAV) ``` 关键代码: | 阶段 | 位置 | |------|------| | 入站 | `core/connection.py` `_route_message` | | VAD/ASR | `core/handle/receiveAudioHandle.py` | | 意图 | `core/handle/intentHandler.py` | | LLM | `ConnectionHandler.chat` | | TTS 出站 | `core/handle/sendAudioHandle.py` | | 上报 | `core/handle/reportHandle.py` → WAV | | 工具 | `plugins_func/*` + `unified_tool_handler.py` | **已有可复用:** Agent、Plugin、MCP、function call、session_id、可选 WAV、`play_music`(但走 TTS 通道,**不适合**复读循环)。 **不存在:** App、Task、Step、TaskInstance、InteractionMode、Assessment、Plan、Subscription。 `session_id` 是 **WS 连接级**,不是 TaskInstance。 --- ## 2. 目标(仍是 V1.0 愿景,但分层落地) ```text Xiaozhi Server ├── Device Gateway ← 现有 WS / MQTT 网关,保留 ├── Chat Orchestration ← 现有 ConnectionHandler.chat ├── Task Runtime ← 新增,权威编排 ├── Media / Audio Object ← 从「聊天附件」演进为可挂 Step 的音频对象 └── Assessment ← Phase 2 起;先文本比对,后语音特征 ``` 最终数据流(目标态,不是 Phase 1): ```text Schedule → TaskInstance → Step → InteractionMode → Device Skill → 儿童语音 → 保存 Audio 对象 → ASR → Assessment → Metric → 家长报告 ``` Phase 1 **截断在**「Mode=PLAY_AUDIO 驱动设备播放」;不要求评估与家长报告。 --- ## 3. 职责边界(与 Client Plan 对齐) ```text Server 创建 / 推进 / 结束 TaskInstance 指定 InteractionMode 调用设备 Skill(MCP 或 type=task) ASR / LLM / TTS / Assessment 音频对象与 step 关联 App JSON 与资源 URL 的权威 Client 执行 Skill、切换 Mode、回报 Event 不持久化 Step 历史、不评分 ``` **禁止**在 Python 对话热路径里写死 `if english_repeat` 作为长期形态;Phase 1 允许一个 **编排插件**,Phase 2 必须升到 Task Runtime + JSON 定义。 --- ## 4. 逻辑模块 vs 物理部署 V1.0 列出的 Gateway / Task Service / Assessment Service / Emotion … **全部作为逻辑边界**。 物理部署保持: ```text xiaozhi-server (Python) 实时:连接、ASR、LLM、TTS、Mode 切换、Task 步进(运行时) manager-api (Java) 配置与持久化:User、Device、Agent、Task 定义/实例、音频元数据、评估结果 对象存储(Phase 2+) 原始音频;第一期可继续 BLOB,但 API 按 audio_id 访问 ``` 不要为第一个复读 Task 拆新进程。 模块映射: | 逻辑模块 | Phase | 落点 | |----------|-------|------| | Device Gateway | 0 已有 | `websocket_server.py` / MQTT gateway | | Chat Orchestration | 0 已有 | `ConnectionHandler` | | Interaction Mode 开关 | 1 | `ConnectionHandler` 上 `interaction_mode`;非 CHAT 跳过 `chat()` | | Repeat Orchestrator | 1 | `plugins_func` 或独立薄模块;MCP `self.mp3.play` | | Task Runtime | 2 | Python 运行时状态机 + Java 实例表 | | Audio Object | 2 | 扩展上报:挂 `step_instance_id`;逐步迁出 LONGBLOB | | Assessment v0 | 2 | expected_text vs ASR;Java 或 Python 均可,结果入库 | | App / Resource | 3 | 吸收 smartlearning 清单 + 下载 URL;Java 为权威 | | Scheduler / Plan | 3 | manager-api + 定时;不要用设备 RTC 闹钟冒充 | | Voice Feature / Baseline / Emotion | 3+ | 异步队列;禁止进实时 TTS 路径 | | Subscription / Developer API | 更后 | 不做 Phase 1–2 | --- ## 5. 现有对话链路:必须保留 无 Task 时行为与现网一致: ```text listen → ASR → intent/LLM/tools → TTS ``` 协议 `hello/listen/abort/iot/mcp/ping` 与下行 `stt/tts/llm/mcp` **禁止改语义**。 插入方式: ```text _route_message / startToChat / chat() 之前判断 interaction_mode CHAT → 原路径 PLAY_AUDIO / FOLLOW_READ → 不调用 chat() FOLLOW_READ 的语音仍走 ASR.receive_audio,结果写入 Step 而非 Dialogue ``` `play_music` 继续服务「用户说播放歌曲」。**不要**用它实现英语复读(它占用 TTS 队列,和课程 SD 播放冲突)。 --- ## 6. Agent vs App | | Agent(已有 `ai_agent`) | App(新建,Phase 3) | |--|--------------------------|----------------------| | 是什么 | 人设 + ASR/LLM/TTS/插件配置 | 业务容器(英语老师、口算…) | | 谁用 | 每台设备当前绑定一个 | 儿童可订阅多个,同时只 Active 一个 | | Phase 1 | **继续用 Agent 跑 Chat** | 不建 App 表也可以做复读 Demo | Phase 1 复读可以挂在「当前 Agent + 一个插件」,不必先有 App 实体。Phase 3 再把 Task Definition 归到 App 下。 不要把 `ai_agent` 改名为 App,也不要在 Agent 上堆 Workflow JSON 作为长期模型(临时可以,正式模型见第 10 节)。 --- ## 7. Task Runtime(Server 权威) ### 7.1 职责 ```text 加载 Task Definition 创建 TaskInstance 按 Step 推进 设置 Connection.interaction_mode 下发 skill.execute / MCP 等待 Device Event 或 ASR 完成 写 StepInstance 决定下一步 / 结束 / 失败 ``` 运行时可活在 `ConnectionHandler` 旁的 `TaskRuntime` 对象(每连接一个当前 Instance)。持久化在 manager-api。 ### 7.2 状态 ```text CREATED → READY → RUNNING → WAITING_DEVICE | WAITING_USER | WAITING_AI → COMPLETED | FAILED | CANCELLED | TIMEOUT ``` 设备只缓存 RUNNING 的当前 step;重连后 Server 重放或结束。 ### 7.3 Definition 与 Instance 分离(保留 V1.0) Definition:模板(英语复读 10 遍)。 Instance:某日某设备的一次执行(进度、状态、结果)。 ### 7.4 Phase 1 特例 没有正式 Runtime 也可以: ```text 插件 english_repeat(n, resource) loop: MCP play → wait play.completed 或超时 mode=PLAY_AUDIO 期间禁止 chat() ``` 这是为了验证链路;Phase 2 用 JSON Step 替换该循环。 --- ## 8. InteractionMode 在 Server 的含义 Server 是 Mode 的 **指定者**: ```text Step.mode = PLAY_AUDIO | FOLLOW_READ | CHAT | LISTEN | … → conn.interaction_mode = … → 可选下行 mode.set(Phase 2) ``` | Mode | Server 行为 | |------|-------------| | CHAT | 现有 `startToChat` / `chat()` | | PLAY_AUDIO | 不 ASR 对话、不 LLM;驱动 play;等完成事件 | | FOLLOW_READ | 先 play,再收听;ASR 文本进 Step;**不**进 Dialogue/LLM | | LISTEN | 只 ASR,结果给当前 Step | | QUIZ/GAME | Phase 3:出题在 Server,设备 CHAT 或 TTS+LISTEN | V1.0 的「Interaction Runtime 作为独立服务」降级为 **ConnectionHandler 上的 mode 字段 + TaskRuntime 策略表**。 --- ## 9. 音频架构 ### 9.1 现状 - 入:Opus 流;内部 ASR PCM 16k - 出:Opus(hello 里常见 24k / 60ms) - 落库:`chat_history_conf==2` 时 PCM/Opus **转 WAV** 进 `ai_agent_chat_audio.audio`(注释写 opus,实际 WAV) - `audio_id` 仅在上报后存在;history 有 `session_id`,**无** task/step ### 9.2 原则(保留 V1.0) > 跟读/评估不要只存 ASR 文本。原始音频是可重分析资产。 但 Phase 1 复读 **可以不存用户音频**(用户可能没说话)。 ### 9.3 演进 **Phase 2 最小:** ```text 沿用 reportHandle WAV 上报 字段增加 task_instance_id, step_instance_id, purpose=follow_read audio_id 写入 step_instance 仍可暂存 BLOB ``` **Phase 2+ 目标(V1.0 对象存储):** ```text DB: audio_id, user/device, task/step, storage_key, duration, format, sample_rate Blob: 对象存储 raw wav/opus 不要把长期课程音频继续堆在 MySQL LONGBLOB ``` **Segment:** 跟读一步一段用户音频即可;单词级切分放到评估模型需要时再做。不要 Phase 2 就上完整 AudioSession 树。 **设备文件录音 / pending 上传:** Client Phase 3;Server 预留 upload API,不阻塞跟读 v0。 时间戳:连接级足够做 v0;NTP 与精确 latency 放评估 Phase 3。Sening 已有时钟同步指令,可复用,不算 Server 新服务。 --- ## 10. 数据模型 ### 10.1 现有表(保留,Chat 继续用) | 表 | 作用 | |----|------| | `sys_user` | 账号 | | `ai_device` | 设备;`user_id` `agent_id` `mac_address` | | `ai_agent` | 人设与模型;`chat_history_conf` | | `ai_agent_chat_history` | 会话文本;`session_id` `audio_id` `chat_type` | | `ai_agent_chat_audio` | 音频 BLOB | | `ai_agent_plugin_mapping` | Agent ↔ 插件 | | `ai_ota` / 模型 / RAG 等 | 与 Task 无关,不动 | Chat 历史 **不要**改语义去存 Task;用新表或可空外键扩展。 ### 10.2 Phase 2 最小新表 ```text task_definition id, app_id(可空), name, version, json_body, interaction 默认 mode task_instance id, device_id, task_definition_id, session_id, status, current_step, started_at, ended_at step_instance id, task_instance_id, step_index, mode, status, audio_id, asr_text, result_json, started_at, ended_at ``` 第一期 `json_body` 可内嵌 steps,不必先拆 `task_step` 定义表。 ### 10.3 Phase 3+(V1.0 清单的子集,按需) ```text app / app_version / app_subscription plan / schedule audio_segment # 从 chat_audio 迁出 assessment / assessment_metric user_metric_baseline daily_summary ``` V1.0 的 emotion_signal、developer 宽表 **不要**列入 Phase 2 DDL。 Child 实体:现网是 User+Device。Phase 3 若需要「一个家长多个孩子」再拆 `child`;Phase 1–2 用 `user_id + device_id`。 --- ## 11. Assessment 保留 V1.0 原则,收紧落地。 ### 11.1 三层(概念保留) ```text L1 任务对错 / 文本匹配 ← Phase 2 v0 L2 语音特征(语速、停顿) ← 异步,Phase 3 L3 趋势 / 基线 / 参与度 ← 要有足够 Instance ``` 情绪只做 **信号** 不是诊断(V1.0 §20 保留),且 **晚于** L1。 ### 11.2 英语跟读 v0(Phase 2) ```text expected_text vs ASR text → 准确率 / 是否说完 → 即时 TTS 反馈(走现有 TTS) → 结果写入 step_instance.result_json ``` 不在 Phase 2 上发音模型、音素对齐、word timing(有则存 ASR 原始 JSON,评估器后补)。 ### 11.3 口算 / 倒背 / 成语 归 Phase 3+ Task Definition,用 QUIZ/GAME + ASR 解析。**不要**为它们先改 Client。 ### 11.4 评估版本化(保留 V1.0) ```text assessment.model_version 同一 step 可追加新评估行,禁止覆盖旧结果 ``` 实时路径只做 L1;L2+ 走队列,避免卡住 TTS。 --- ## 12. 协议与 API ### 12.1 设备协议 Phase 1:只增加 **MCP 调用**(已有 `type=mcp`)。Server 主动 `tools/call` 播放。 Phase 2:与 Client Plan 相同的 `task.*` / `mode.set` / 完成事件。Python `TextMessageType` 增加枚举,Handler 与 `hello/listen/mcp` 并列。 二进制 Opus **不改**。MQTT 网关 16 字节头逻辑保持。 ### 12.2 HTTP API(manager-api) Phase 1:可无新 API(插件 + 管理端现有配置足够触发 Demo)。 Phase 2: ```text POST /task/instance 启动(deviceId, taskId, params) GET /task/instance/{id} 查询状态与 steps POST /agent/chat-history/report 扩展可选 task/step 字段(兼容旧客户端) ``` Phase 3: ```text /apps /resources /schedule /assessment /reports ``` V1.0 一次铺开 `/analytics` `/developer` —— 后置。 上报仍走现有 `manage_api_client.report`,避免再造一条聊天管道。 --- ## 13. Plugin / MCP 复用 ```text Server Plugin 编排、业务判断、调 ASR 结果 Device MCP 播放、音量、屏、下载(设备 Skill) Server MCP 外部工具,与 Task 无强绑定 ``` 英语复读 Phase 1:Server Plugin 调 Device MCP。 不要把复读写进固件,也不要新建与 `UnifiedToolHandler` 平行的第二套工具总线。 --- ## 14. smartlearning 吸收策略 | 已有能力 | 归属 | |----------|------| | 课程 JSON / manifest | Phase 3 App Resource;权威迁入 manager-api | | MQTT DOWNLOAD / PLAY_MEDIA | Client 板级已实现;Server 最终经 Xiaozhi 连接下发同等语义 | | 场景识别 HTTP | 不进入 Xiaozhi 主链路,除非产品明确要 | **禁止**让 Xiaozhi Server 与 smartlearning 长期双写同一设备状态。Phase 1 Demo 若只能跑 Sening,允许暂时打 snlp,但 Coding Plan 必须标明技术债。 --- ## 15. 分阶段(Server) 与 Client Plan 编号一致。 ### Phase 1 — 复读编排,不破坏 Chat - `ConnectionHandler.interaction_mode`:`CHAT` | `PLAY_AUDIO` - PLAY_AUDIO 时:不 `chat()`、不把课程当用户意图 - 插件:循环 N 次 MCP/`PLAY_MEDIA`(或等价) - **不建** Task 表、不评估、不改 history 语义、不拆微服务 - Chat 回归:mode 回 CHAT 或连接仍按现网 listen 验收:管理端或一句触发可让设备复读;同时唤醒聊天与现网一致。 ### Phase 2 — Task Runtime 切片 + 跟读 v0 - `task_definition` / `task_instance` / `step_instance` - 协议 `task.*` + `mode.set`(若 MCP 不够表达步进) - FOLLOW_READ:ASR 写入 step;扩展 report 关联 audio_id - Assessment v0:文本比对 + 结果 JSON - 仍不要求对象存储、基线、情绪、家长小程序 验收:一次跟读 Instance 可查询到各 step 的音频与文本与分数。 ### Phase 3 — 平台与评估深化 - App / Resource Sync(吸收课程清单) - Schedule / Plan(家长「每天 19:30」) - 音频迁对象存储 - 异步 Voice Feature;可选 Baseline - QUIZ 等新 Task 只加 Definition,不加固件业务 - 再往后:Emotion Signal、Subscription、Developer API(V1.0 后半段) --- ## 16. 禁止事项(Server) 1. 为 Task 重写 ASR/TTS Provider 或 WS 帧格式 2. 用 `play_music` 冒充课程复读 3. 把 Task 行塞进 `ai_agent_chat_history` 当正式模型 4. Phase 1 上情绪/心理/参与度大盘 5. Phase 1 拆微服务 / 上独立 MQ 评估集群 6. 改 hello/listen/tts 字段含义 7. 让设备成为 Step 权威 8. 未读 `ConnectionHandler` 就新写一套对话服务器 9. 与 smartlearning 永久双栈调度 --- ## 17. AI Coding 约束 1. 先读 `connection.py`、`receiveAudioHandle.py`、`reportHandle.py`、Agent 上报 API、现有表结构。 2. 默认 Chat 必须可关 Task 独立运行。 3. 实时路径只做 Mode 分流与 L1 评估;重分析异步。 4. 新表新协议都要向后兼容旧设备(忽略未知 type / 可空 task 字段)。 5. Phase 1 能用插件解决则不改 Java DDL。 --- ## 18. 与 Client Plan 的契约 Server 保证: - CHAT 连接行为向后兼容 - 非 CHAT mode 不误触发 LLM - Instance 权威状态可查询 - Phase 2 音频能挂到 step Server 要求 Client: - 提供可调用的 play_audio(至少一块目标板) - FOLLOW_READ 时继续上传 Opus - 完成事件可关联 session / instance - 不在固件写评估 统一验收链(目标态): ```text Server 创建 Instance → mode + skill → Device 执行 → Event/Audio → ASR → Assessment → 查询 Instance ``` Phase 1 只验证前半:创建意图 → play 循环 → Chat 仍可用。 --- ## 19. 下一阶段 下一步不是实现评估平台,而是产出: ```text Phase 1 Coding Plan ├── Server:mode 旁路 + 复读插件 ├── Client:复用 MCP/PLAY_MEDIA + 音频互斥(已有 PauseSdCardMp3) └── 回归:现有 Chat E2E ``` V1.0 中的家长报告分级、跨任务分析、开发者分成等,保留为 Phase 3 以后的产品附录,不进入近期架构施工图。